﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace SBS_Map_Editor
{
    public class ModelNode : IBoundedTransform
    {
        #region Variables
        private static uint sPickID = 1;

        private Model mModel;
        private BoundingBox mBounds;

        private Vector3 mTranslation;
        private Quaternion mRotation;
        private Vector3 mScale;

        private uint mPickID;
        private bool mPickEnabled;
        #endregion

        #region Properties
        public Model Model
        {
            get { return mModel; }
            set
            {
                mModel = value;
                CalculateBounds();
            }
        }

        public BoundingBox Bounds { get; set; }
        public Vector3 Translation { get; set; }
        public Quaternion Rotation { get; set; }
        public Vector3 Scale { get; set; }

        public Matrix WorldMatrix
        {
            get
            {
                return Matrix.CreateScale(mScale)
                        * Matrix.CreateFromQuaternion(mRotation)
                        * Matrix.CreateTranslation(mTranslation);
            }
        }

        public uint PickID { get; set; }
        public bool PickEnabled { get; set; }
        #endregion

        #region Constructor
        public ModelNode()
            : this(null)
        {
        }

        public ModelNode(Model model)
            : this(model, Vector3.Zero, Quaternion.Identity, Vector3.One)
        {

        }

        public ModelNode(Model model, Vector3 pos, Quaternion rot, Vector3 scale)
        {
            mPickID = sPickID++;
            mPickEnabled = true;

            mModel = model;

            mTranslation = pos;
            mRotation = rot;
            mScale = scale;

            CalculateBounds();
        }
        #endregion

        #region Action Functions
        public void Draw(ICamera camera)
        {
            if (mModel == null)
                return;

            foreach (ModelMesh mesh in mModel.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.View = camera.ViewMatrix;
                    effect.Projection = camera.ProjectionMatrix;
                    effect.World = WorldMatrix;

                    effect.EnableDefaultLighting();

                    effect.LightingEnabled = true;
                    effect.PreferPerPixelLighting = true;
                    effect.TextureEnabled = true;
                    effect.VertexColorEnabled = true;
                    effect.DiffuseColor = Color.White.ToVector3();
                }

                mesh.Draw(SaveStateMode.SaveState);
            }
        }

        public void DrawPick(PickBuffer pick_buf, ICamera camera)
        {
            if (mModel == null)
                return;

            if (!mPickEnabled)
                return;

            foreach (ModelMesh mesh in mModel.Meshes)
            {
                pick_buf.PushMatrix(MatrixMode.World, WorldMatrix);
                pick_buf.PushPickID(mPickID);

                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    pick_buf.PushVertexDeclaration(part.VertexDeclaration);

                    pick_buf.QueueIndexedPrimitives(PrimitiveType.TriangleList, part.StreamOffset,
                        part.BaseVertex, 0, part.NumVertices, part.StartIndex, part.PrimitiveCount);

                    pick_buf.PopVertexDeclaration();
                }

                pick_buf.PopVertexBuffer();
                pick_buf.PopIndexBuffer();
            }

            pick_buf.PopPickID();
            pick_buf.PopMatrix(MatrixMode.World);
        }
        #endregion

        #region Helper Functions
        private void CalculateBounds()
        {
            if (mModel == null)
            {
                mBounds = new BoundingBox();
                return;
            }

            Vector3 min = Vector3.One * float.MaxValue;
            Vector3 max = Vector3.One * float.MinValue;

            foreach (ModelMesh mesh in mModel.Meshes)
            {
                byte[] data = new byte[mesh.VertexBuffer.SizeInBytes];
                mesh.VertexBuffer.GetData(data);

                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    VertexDeclaration decl = part.VertexDeclaration;
                    VertexElement[] elem = decl.GetVertexElements();

                    int stride = decl.GetVertexStrideSize(0);

                    short pos_at = -1;
                    for (int i = 0; i < elem.Length; i++)
                    {
                        if (elem[i].VertexElementUsage != VertexElementUsage.Position)
                            continue;

                        pos_at = elem[i].Offset;
                        break;
                    }

                    if (pos_at == -1)
                        throw new Exception("No position data?!?!");

                    for (int i = 0; i < data.Length; i++)
                    {
                        int ind = i + pos_at;
                        int fs = sizeof(float);

                        float x = BitConverter.ToSingle(data, ind);
                        float y = BitConverter.ToSingle(data, ind + fs);
                        float z = BitConverter.ToSingle(data, ind + (fs * 2));

                        if (x < min.X) min.X = x;
                        if (x > max.X) max.X = x;
                        if (y < min.Y) min.Y = y;
                        if (y > max.Y) max.Y = y;
                        if (z < min.Z) min.Z = z;
                        if (z > max.Z) max.Z = z;
                    }
                }
            }

            mBounds = new BoundingBox(min, max);
        }

        public float? Intersects(Ray ray)
        {
            if (mModel == null)
                return null;

            Matrix world = Matrix.CreateScale(mScale) * Matrix.CreateFromQuaternion(mRotation) * Matrix.CreateTranslation(mTranslation);

            Matrix invWorld = Matrix.Invert(world);

            ray.Position = Vector3.Transform(ray.Position, invWorld);
            ray.Direction = Vector3.Normalize(Vector3.TransformNormal(ray.Direction, invWorld));

            float? closest = null;
            foreach (ModelMesh mesh in mModel.Meshes)
            {
                float? isect = mesh.BoundingSphere.Intersects(ray);
                if (isect.HasValue)
                {
                    if ((closest == null) || (isect.Value < closest.Value))
                        closest = isect;
                }
            }

            return closest;
        }
        #endregion
    }
}
